/*
 * Smart Switch
 * Designed by ZhangHao
 * 2018.12.03
 * 
 * Note:
 * 1.实现MQTT连接，协议定义与解析。
 * 2.实现OTA升级功能
 * 3.DHT11温湿度传感器驱动
 * 4.OLED显示屏驱动
 * 5.光敏电阻测环境光亮度
 * 6.电容式触摸传感器
 * 
 * 
 * 详见ReadMe.md文件
 */

/*
include files
*/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include <DHT.h>
#include <Ticker.h>

#define LED0_Pin 14
#define LED1_Pin 16
#define KEY0_Pin 13
#define KEY1_Pin 12
#define DHTPIN 5 
#define BEEP_Pin 4
#define OLED_SDA_Pin 0
#define OLED_SCL_Pin 2

#define DHTTYPE DHT11   // DHT 11

//WIFI信息，可由配置按键修改
const char *ssid = "";
const char *password = "";
const char *mqtt_server = ""; //EMQ
const char *inTopic = "SW_In_8266"; 
const char *outTopic = "SW_Out_8266"; 

//Global variables 全局变量
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);
long lastMsg = 0;
char msg[50];
int value = 0;
static char wbuf[128];
char MAC_adress[20] = {'\0'}; //不知道如何读MAC地址，待完成
int A00 = 0;                  //当前温度 - 只读
int A11 = 0;                  //当前湿度 - 只读
int A22 = 0;                  //光敏电阻 - 只读
int A33 = 0;                  //触摸开关1状态
int A44 = 0;                  //触摸开关2状态
int A55 = 0;                  //Beep 蜂鸣器 0 - 不响  1 - 长响  2 - 短响一声  3 - 长响一声
int A66 = 0;                  //透传显示
char data_aa[64];             //透传数据
short D00 = 0;                //默认不主动上报数据
short V0 = 10;                //10秒上报间隔

int Get_light();
float Get_Temperature();
float Get_Humidity();
void beep_func(int tt);
void beep_close();
void LED_close();

Ticker timer1(beep_close,100);
Ticker timer2(beep_close,1500);
Ticker timer3(LED_close,200);

//Wifi初始化，连接到路由器
void setup_wifi()
{

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void wifi_print(char *payload)
{
  client.publish("infoTopic",payload);
}
//OTA升级  参数为要升级的文件路径
void OTA_Update(char *url)
{
  Serial.print("OTA_URL:");
  Serial.println(url);
  //如果url为0，则使用默认固件
  if (url[0] == 0)
  {
    sprintf(url, "http://39.106.187.215/Smart_light/Smart_light_V1.0.bin");
  }
  t_httpUpdate_return ret = ESPhttpUpdate.update(url);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED:
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES:
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK:
    Serial.println("[update] Update ok."); // may not called we reboot the ESP
    break;
  }
}

void report_data()
{
  String data = "{";
  if (D00 && 0x01)
  {
    data += "A0=";
    data += A00;
  }
  if (D00 && 0x02)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A1=";
    data += A11;
  }
  if (D00 && 0x04)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A2=";
    data += A22;
  }
  if (D00 && 0x08)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A3=";
    data += A33;
  }
  if (D00 && 0x10)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A4=";
    data += A44;
  }
  if (D00 && 0x20)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A5=";
    data += A55;
  }
  if (D00 && 0x40)
  {
    if (data.length() > 1)
    {
      data += ",";
    }
    data += "A6=";
    data += A66;
  }
  data += "}";
  if (D00)
  {
    int ii = 0;
    char datata[64] = {'\n'};
    int len = data.length();
    for (ii = 0; ii < len; ii++)
    {
      datata[ii] = data[ii];
    }
    client.publish("SL_OUT_8266", datata); //主动推送被查询的消息   话题后期使用MAC地址命名
  }
}

//用户信息处理
int usr_process_command_call(char *ptag, char *pval, char *pout)
{
  int val;
  int ret = 0;
  val = atoi(pval);
  //控制命令解析
  if (0 == strcmp("CD0", ptag))
  {              //若检测到CD0指令
    D00 &= ~val; //关闭主动上报
  }
  if (0 == strcmp("OD0", ptag))
  {             //若检测到OD0指令
    D00 |= val; //开启主动上报
  }
  if (0 == strcmp("D0", ptag))
  { //若检测到D0指令
    if (0 == strcmp("?", pval))
    {                                    //若检测到询问指令
      ret = sprintf(pout, "D0=%u", D00); //向pout以"D0=%u"赋值输出D0参数
    }
  }
  if (0 == strcmp("A0", ptag))
  { //若检测到A0指令  温度
    if (0 == strcmp("?", pval))
    {
      //TODO：获取当前温度
      A00 = int(Get_Temperature()*10);
      ret = sprintf(pout, "A0=%1.2f", Get_Temperature());
    }
  }
  if (0 == strcmp("A1", ptag))
  { //若检测到A1指令  湿度
    if (0 == strcmp("?", pval))
    {
      //TODO：获取当前湿度
      A11 = int(Get_Humidity()*10);
      ret = sprintf(pout, "A1=%1.2f", Get_Humidity());
    }
  }
  if (0 == strcmp("A2", ptag))
  { //若检测到A2指令
    //在此检测光敏电阻
    A22 = Get_light();
    ret = sprintf(pout, "A2=%u", A22);
  }

  if (0 == strcmp("A3", ptag))
  {  //触摸开关1
    if (0 == strcmp("?", pval))
    {
      ret = sprintf(pout, "A3=%u", A33);
    }
  }

  if (0 == strcmp("A4", ptag))
  {  //触摸开关2
    if (0 == strcmp("?", pval))
    {
      ret = sprintf(pout, "A4=%u", A44);
    }
  }

  if (0 == strcmp("A5", ptag))
  {  //蜂鸣器
    if (0 == strcmp("?", pval))
    {
      ret = sprintf(pout, "A5=%u", A55);
    }
    else
    {
      //调用蜂鸣器驱动函数
      if(val==0 || val==1 || val==2 || val==3){
        beep_func(val);
        A55 = val;
      }
    }
  }

  if (0 == strcmp("A6", ptag))
  {  //保留测试
    if (0 == strcmp("?", pval))
    {
      ret = sprintf(pout, "A6=%u", A66);
    }
    else
    {
      A66 = val;
    }
  }

  if (0 == strcmp("AA", ptag))
  {
    if (0 == strcmp("?", pval))
    {
      ret = sprintf(pout, "AA=%s", data_aa);
    }
    else
    {
      //透传数据
      sprintf(data_aa, pval);
    }
  }

  if (0 == strcmp("V0", ptag))
  { //若检测到V0指令
    if (0 == strcmp("?", pval))
    {                                   //若检测到询问指令
      ret = sprintf(pout, "V0=%u", V0); //向pout以"V0=%u"赋值输出V0参数
    }
    else
    {
      if (val > 0 && val < 1000)
      {
        V0 = val;
      } //否则更新数据上传时间间隔
    }
  }

  return ret;
}

//处理通用消息
static int _process_command_call(char *ptag, char *pval, char *pout)
{
  int val;
  int ret = 0;

  val = atoi(pval);
  val = val;
  if (0 == strcmp("ECHO", ptag))
  { //ECHO测试连通性，数据原样返回
    if (0 == strcmp("RST", pval))
    {
      ESP.restart();
    }
    ret = sprintf(pout, "ECHO=%s", pval);
  }
  else if (0 == strcmp("MAC", ptag))
  { //查询模块MAC地址
    ret = sprintf(pout, "MAC=%s", MAC_adress);
  }
  else if (0 == strcmp("IP", ptag))
  { //查询模块IP地址
    ret = sprintf(pout, "IP=%s", "0.0.0.0");
  }
  else if (0 == strcmp("TYPE", ptag))
  { //设备串号
    if (0 == strcmp("!", pval))
    {
      ret = sprintf(pout, "TYPE=Smart_Switch");
    }
  }
  else if (0 == strcmp("OTA", ptag))
  { //设备OTA升级
    OTA_Update(pval);
  }
  else
  {
    ret = usr_process_command_call(ptag, pval, pout);
  }
  return ret;
}

//MQTT消息的回调函数，用来处理收到的消息
void callback(char *topic, byte *payloadbyte, unsigned int length)
{
  char *p;
  char *ptag = NULL;
  char *pval = NULL;
  char *pwbuf = wbuf + 1;
  char payload[200] = {'\0'};
  digitalWrite(LED1_Pin,HIGH);//点亮接受信息指示灯
  timer3.start();
  //向串口打印收到的消息
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("--");
  Serial.print(length);
  Serial.print("] ");
  if (length > 200)
    return;
  for (unsigned int i = 0; i < length; i++)
  {
    payload[i] = (char)payloadbyte[i];
    Serial.print((char)payload[i]);
  }
  Serial.println();

  //处理消息
  if (payload[0] != '{' || payload[length - 1] != '}')
  {
    Serial.println("Error MQTT CMD.");
  }

  payload[length - 1] = 0;
  p = payload + 1;
  do
  {
    ptag = p;
    p = strchr(p, '=');
    if (p != NULL)
    {
      *p++ = 0;
      pval = p;
      p = strchr(p, ',');
      if (p != NULL)
      {
        *p++ = 0;
      }
      int ret;
      ret = _process_command_call(ptag, pval, pwbuf);
      if (ret > 0)
      {
        pwbuf += ret;
        *pwbuf++ = ',';
      }
    }
  } while (p != NULL);
  if ((pwbuf - wbuf) > 1)
  { //如果有返回值，则构建返回值数据
    wbuf[0] = '{';
    pwbuf[0] = 0;
    pwbuf[-1] = '}';
    client.publish(outTopic, wbuf); //推送被查询的消息
  }
  
}

//MQTT自动重连函数
void reconnect()
{
  // Loop until we're reconnected
  while (!client.connected())
  {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "Smart_Switch_Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str()))
    {
      digitalWrite(LED0_Pin,HIGH);//入网  点亮LED灯
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("infoTopic", "Smart_Switch Online now.");
      // ... and resubscribe
      client.subscribe(inTopic);
    }
    else
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

//获取当前光照值
int Get_light()
{
  return analogRead(A0);
}
//获取温度
float Get_Temperature()
{
  return dht.readTemperature();
}
//获取湿度
float Get_Humidity()
{
  return dht.readHumidity();
}
//按键扫描函数   0-无按键按下  1-KEY0被按下  2-KEY1被按下
 int keyScan()
 {
   int temp1 = digitalRead(KEY0_Pin);
   int temp2 = digitalRead(KEY1_Pin);
   if(temp1 == 1 || temp2 == 1)
   {
     delay(10);
     temp1 = digitalRead(KEY0_Pin);
     temp2 = digitalRead(KEY1_Pin);
     if(temp1 == 1){
       while(digitalRead(KEY0_Pin));
       return 1;
     }
     if(temp2 == 1){
       while(digitalRead(KEY1_Pin));
       return 2;
     }
   }
   return 0;
 }
 void LED_close()
 {
   digitalWrite(LED1_Pin,LOW);//熄灭接收指示灯
   timer3.stop();
 }
 void beep_close()
 {
   digitalWrite(BEEP_Pin,HIGH);
   timer1.stop();
   timer2.stop();
 }
 void beep_func(int tt)
 {
   //0-不响  1-长响 2-短响一声 3-长响一声
  switch(tt){
    case 0 : digitalWrite(BEEP_Pin,HIGH); break;
    case 1 : digitalWrite(BEEP_Pin,LOW); break;
    case 2 : digitalWrite(BEEP_Pin,LOW); timer1.start(); break;
    case 3 : digitalWrite(BEEP_Pin,LOW); timer2.start(); break;
    default : break;
  }
 }
 void changeLight(){
   if(A33 == 0){
     //在此发送开灯命令
     client.publish("SL_IN_8506", "{A0=255}"); 
     client.publish(outTopic, "{Light ON}"); 
     A33 = 1;
     return;
   }
   if(A33 == 1){
     //在此发送关灯命令
     client.publish("SL_IN_8506", "{A0=0}"); 
     client.publish(outTopic, "{Light OFF}"); 
     A33 = 0;
     return;
   }
 }
 void My_OLED_display(){

   //五秒自动息屏
 }
/**
 * 转换重连wifi的方法，新的操作逻辑为：
 * 在上电时检测KEY1的状态，如果为高，则按照预设参数正常启动，
 * 如果为低，则开启AP模式，启动HTTP Server，配置SSID和Password。
//按键检测
 void Key_scan(){
   if(digitalRead(Key_Pin)==0){
     delay(10);
     if(digitalRead(Key_Pin)==0){
       //转为AP模式，获取SSID和password之后重新连接WiFi
       //TODO
     }
   }
 }
**/
void setup()
{
  //初始化IO口
  pinMode(LED0_Pin, OUTPUT);
  pinMode(LED1_Pin, OUTPUT);
  pinMode(BEEP_Pin, OUTPUT);
  pinMode(KEY0_Pin, INPUT);
  pinMode(KEY1_Pin, INPUT);

  digitalWrite(LED0_Pin,LOW);//入网指示灯
  digitalWrite(LED1_Pin,LOW);//接收指示
  digitalWrite(BEEP_Pin,HIGH);//接收指示

  //初始化串口  输出日志
  Serial.begin(115200);
  dht.begin();
  setup_wifi();
  client.setServer(mqtt_server, 1883); //连接服务器
  client.setCallback(callback);        //设置回调函数
}

void loop()
{

  int temp = keyScan();
  //digitalWrite(LED1_Pin,temp);
  switch(temp){
    case 0 :  break;
    case 1 :  //当按键1被按下  改变灯的状态
              Serial.printf("Key 1 Pressed.");
              changeLight();
              beep_func(2);
              break;
    case 2 : //当按键2被按下  点亮OLED屏幕  显示信息
              Serial.printf("Key 2 Pressed.");
              My_OLED_display();
              break;
    default : Serial.printf("default Key Pressed.");break;
  }

  timer1.update();
  timer2.update();
  timer3.update();

  if (!client.connected())
  { //检查是否掉线，自动重连
    digitalWrite(LED0_Pin,LOW);
    reconnect();
  }

  client.loop();
}
